/*
 * Tencent is pleased to support the open source community by making
 * MMKV available.
 *
 * Copyright (C) 2018 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *       https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tencent.mmkv;


import com.ohos.afkt.BuildConfig;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.DataAbilityRemoteException;
import ohos.aafwk.ability.PathMatcher;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.bundle.AbilityInfo;
import ohos.bundle.BundleInfo;
import ohos.bundle.IBundleManager;
import ohos.data.DatabaseHelper;
import ohos.data.dataability.DataAbilityPredicates;
import ohos.data.preferences.Preferences;
import ohos.data.rdb.ValuesBucket;
import ohos.data.resultset.ResultSet;
import ohos.data.resultset.TableResultSet;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.hiviewdfx.HiLog;
import ohos.rpc.RemoteException;
import ohos.utils.PacMap;
import ohos.utils.net.Uri;

import java.io.FileDescriptor;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static ohos.event.commonevent.CommonEventManager.*;

/**
 * 使用ContentProvider实现多进程SharedPreferences读写;<br>
 * 1、ContentProvider天生支持多进程访问；<br>
 * 2、使用内部私有BroadcastReceiver实现多进程OnSharedPreferenceChangeListener监听；<br>
 * <p>
 * 使用方法：Manifest.xml中添加provider申明：<br>
 * <pre>
 * &lt;provider name="com.tencent.mm.sdk.patformtools.MultiProcessSharedPreferences"
 * authorities="com.tencent.mm.sdk.patformtools.MultiProcessSharedPreferences"
 * exported="false" /&gt;
 * &lt;!-- authorities属性里面最好使用包名做前缀，apk在安装时authorities同名的provider需要校验签名，否则无法安装；--!/&gt;<br>
 * </pre>
 * <p>
 * ContentProvider方式实现要注意：<br>
 * 1、当ContentProvider所在进程os.Process.killProcess(pid)时，会导致整个应用程序完全意外退出或者ContentProvider所在进程重启；<br>
 * 重启报错信息：Acquiring provider <processName> for user 0: existing object's process dead；<br>
 * 2、如果设备处在“安全模式”下，只有系统自带的ContentProvider才能被正常解析使用，因此put值时默认返回false，get值时默认返回null；<br>
 * <p>
 * 其他方式实现SharedPreferences的问题：<br>
 * 使用FileLock和FileObserver也可以实现多进程SharedPreferences读写，但是会有兼容性问题：<br>
 * 1、某些设备上卸载程序时锁文件无法删除导致卸载残留，进而导致无法重新安装该程序（报INSTALL_FAILED_UID_CHANGED错误）；<br>
 * 2、某些设备上FileLock会导致僵尸进程出现进而导致耗电；<br>
 * 3、僵尸进程出现后，正常进程的FileLock会一直阻塞等待僵尸进程中的FileLock释放，导致进程一直阻塞；<br>
 *
 * @author seven456@gmail.com
 * @version 1.0
 * @since JDK1.6
 */
public class MultiProcessSharedPreferences extends Ability implements Preferences {
    private static final String TAG = "MicroMsg.MultiProcessSharedPreferences";
    public static final boolean DEBUG = BuildConfig.DEBUG;
    private Context mContext;
    private EditorImpl mEditor;
    private String mName;
    private int mMode;
    private boolean mIsSafeMode;
    private List<SoftReference<PreferencesObserver>> mListeners;
    private CommonEventSubscriber mReceiver;

    private static String AUTHORITY;
    private static volatile Uri AUTHORITY_URI;
    private PathMatcher mUriMatcher;
    private static final String KEY = "value";
    private static final String KEY_NAME = "name";
    private static final String PATH_WILDCARD = "*/";
    private static final String PATH_GET_ALL = "getAll";
    private static final String PATH_GET_STRING = "getString";
    private static final String PATH_GET_INT = "getInt";
    private static final String PATH_GET_LONG = "getLong";
    private static final String PATH_GET_FLOAT = "getFloat";
    private static final String PATH_GET_BOOLEAN = "getBoolean";
    private static final String PATH_CONTAINS = "contains";
    private static final String PATH_APPLY = "apply";
    private static final String PATH_COMMIT = "commit";
    private static final String PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER =
            "registerOnSharedPreferenceChangeListener";
    private static final String PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER =
            "unregisterOnSharedPreferenceChangeListener";
    private static final int GET_ALL = 1;
    private static final int GET_STRING = 2;
    private static final int GET_INT = 3;
    private static final int GET_LONG = 4;
    private static final int GET_FLOAT = 5;
    private static final int GET_BOOLEAN = 6;
    private static final int CONTAINS = 7;
    private static final int APPLY = 8;
    private static final int COMMIT = 9;
    private static final int REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 10;
    private static final int UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 11;
    private Map<String, Integer> mListenersCount;
    public DataAbilityHelper dataAbilityHelper;
    public DatabaseHelper databaseHelper;

    private static class ValuesBucketHelper {
        public static ValuesBucket from(HashMap map) {
            ValuesBucket valuesBucket = new ValuesBucket();
            for (Object o : map.entrySet()) {
                Map.Entry entry = (Map.Entry) o;
                String key = (String) entry.getKey();
                Object value = entry.getValue();
                if (value instanceof String) {
                    valuesBucket.putString(key, (String) value);
                } else if (value instanceof Integer) {
                    valuesBucket.putInteger(key, (Integer) value);
                } else if (value instanceof Boolean) {
                    valuesBucket.putBoolean(key, (Boolean) value);
                } else if (value instanceof Byte) {
                    valuesBucket.putByte(key, (Byte) value);
                } else if (value instanceof byte[]) {
                    valuesBucket.putByteArray(key, (byte[]) value);
                } else if (value instanceof Double) {
                    valuesBucket.putDouble(key, (Double) value);
                }
            }
            return valuesBucket;
        }
    }

    private static class ReflectionUtil {

        public static ValuesBucket contentValuesNewInstance(HashMap<String, Object> values) {
            return ValuesBucketHelper.from(values);
        }

        public static Preferences editorPutStringSet(Preferences editor, String key, Set<String> values) {
            try {
                Method method = editor.getClass().getDeclaredMethod(
                        "putStringSet", new Class[]{String.class, Set.class}); //
                return (Preferences) method.invoke(editor, key, values);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        public static void editorApply(Preferences editor) {
            try {
                Method method = editor.getClass().getDeclaredMethod("apply"); //
                method.invoke(editor);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static final String SCHEME_CONTENT = "dataability";

    private void checkInitAuthority(Context context) {
        if (AUTHORITY_URI == null) {
            String AUTHORITY = null;
            Uri AUTHORITY_URI = MultiProcessSharedPreferences.AUTHORITY_URI;
            synchronized (MultiProcessSharedPreferences.this) {
                if (AUTHORITY_URI == null) {
                    AUTHORITY = queryAuthority(context);
                    AUTHORITY_URI = Uri.parse(SCHEME_CONTENT + ":///" + AUTHORITY);
                    if (DEBUG) {
                        //						Log.d(TAG, "checkInitAuthority.AUTHORITY = " + AUTHORITY);
                    }
                }
                if (AUTHORITY == null) {
                    throw new IllegalArgumentException("'AUTHORITY' initialize failed.");
                }
            }
            MultiProcessSharedPreferences.AUTHORITY = AUTHORITY;
            MultiProcessSharedPreferences.AUTHORITY_URI = AUTHORITY_URI;
        }
    }


    @Override
    public void registerObserver(PreferencesObserver preferencesObserver) {
        synchronized (this) {
            if (mListeners == null) {
                mListeners = new ArrayList<SoftReference<PreferencesObserver>>();
            }
            Boolean result = (Boolean) getValue(PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false);
            if (result != null && result) {
                mListeners.add(new SoftReference<PreferencesObserver>(preferencesObserver));
                if (mReceiver == null) {
                    CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(new MatchingSkills());
                    mReceiver = new CommonEventSubscriber(subscribeInfo) {
                        @Override
                        public void onReceiveEvent(CommonEventData commonEventData) {
                            String name = commonEventData.getIntent().getStringParam(KEY_NAME);
                            @SuppressWarnings("unchecked")
                            List<String> keysModified = (List<String>) commonEventData.getIntent().getSerializableParam(KEY);
                            if (mName.equals(name) && keysModified != null) {
                                List arrayListeners;
                                synchronized (MultiProcessSharedPreferences.this) {
                                    arrayListeners = mListeners;
                                }
                                List<SoftReference<PreferencesObserver>> listeners =
                                        new ArrayList<SoftReference<PreferencesObserver>>(arrayListeners);
                                for (int i = keysModified.size() - 1; i >= 0; i--) {
                                    final String key = keysModified.get(i);
                                    for (SoftReference<PreferencesObserver> srlistener : listeners) {
                                        PreferencesObserver listener = srlistener.get();
                                        if (listener != null) {
                                            listener.onChange(MultiProcessSharedPreferences.this, key);
                                        }
                                    }
                                }
                            }
                        }
                    };
                    try {
                        subscribeCommonEvent(mReceiver);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    @Override
    public void unregisterObserver(PreferencesObserver preferencesObserver) {
        synchronized (this) {
            getValue(PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false);
            if (mListeners != null) {
                List<SoftReference<PreferencesObserver>> removing = new ArrayList<>();
                for (SoftReference<PreferencesObserver> srlistener : mListeners) {
                    PreferencesObserver listenerFromSR = srlistener.get();
                    if (listenerFromSR != null && listenerFromSR.equals(preferencesObserver)) {
                        removing.add(srlistener);
                    }
                }
                for (SoftReference<PreferencesObserver> srlistener : removing) {
                    mListeners.remove(srlistener);
                }
                if (mListeners.isEmpty() && mReceiver != null) {
                    try {
                        unsubscribeCommonEvent(mReceiver);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    mReceiver = null;
                    mListeners = null;
                }
            }
        }
    }


    private static String queryAuthority(Context context) {
        BundleInfo packageInfos = null;
        IBundleManager mgr = context.getBundleManager();
        if (mgr != null) {
            try {
                packageInfos = mgr.getBundleInfo(context.getBundleName(), 0x00000008);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        if (packageInfos != null && packageInfos.getAbilityInfos() != null) {
            for (AbilityInfo providerInfo : packageInfos.getAbilityInfos()) {
                if (providerInfo.bundleName.equals(MultiProcessSharedPreferences.class.getName())) {
                    return providerInfo.label;
                }
            }
        }
        return MultiProcessSharedPreferences.class.getName();
    }

    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);

        Util.log("DataAbility onStart");
        dataAbilityHelper = DataAbilityHelper.creator(this);
        databaseHelper = new DatabaseHelper(this);

        checkInitAuthority(this);

        mUriMatcher = new PathMatcher();
        mUriMatcher.addPath(PATH_GET_ALL, GET_ALL);
        mUriMatcher.addPath(PATH_GET_STRING, GET_STRING);
        mUriMatcher.addPath(PATH_GET_INT, GET_INT);
        mUriMatcher.addPath(PATH_GET_LONG, GET_LONG);
        mUriMatcher.addPath(PATH_GET_FLOAT, GET_FLOAT);
        mUriMatcher.addPath(PATH_GET_BOOLEAN, GET_BOOLEAN);
        mUriMatcher.addPath(PATH_CONTAINS, CONTAINS);
        mUriMatcher.addPath(PATH_APPLY, APPLY);
        mUriMatcher.addPath(PATH_COMMIT, COMMIT);
        mUriMatcher.addPath(PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER,
                REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
        mUriMatcher.addPath(PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER,
                UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
    }

    /**
     * mode不使用 特可以支持多进程了；
     *
     * @param mode
     * @see Context#MODE_PRIVATE
     */
    public static Preferences getSharedPreferences(Context context, String name, int mode) {
        return new MultiProcessSharedPreferences(context, name, mode);
    }

    public MultiProcessSharedPreferences() {
    }

    private MultiProcessSharedPreferences(Context context, String name, int mode) {
        mContext = context;
        dataAbilityHelper = DataAbilityHelper.creator(this);
        databaseHelper = new DatabaseHelper(this);
        mName = name;
        mMode = mode;
        mEditor = new EditorImpl();
        IBundleManager mgr = context.getBundleManager();
        if (mgr != null) {
            mIsSafeMode =
                    mgr.isSafeMode(); // 如果设备处在“安全模式”下，只有系统自带的ContentProvider才能被正常解析使用；
        }
    }

    @Override
    public Map<String, ?> getAll() {
        return (Map<String, ?>) getValue(PATH_GET_ALL, null, null);
    }

    @Override
    public Preferences putInt(String s, int i) {
        return mEditor.putInt(s, i);
    }

    @Override
    public Preferences putString(String s, String s1) {
        return mEditor.putString(s, s1);
    }

    @Override
    public Preferences putBoolean(String s, boolean b) {

        return mEditor.putBoolean(s, b);
    }

    @Override
    public Preferences putLong(String s, long l) {

        return mEditor.putLong(s, l);
    }

    @Override
    public Preferences putFloat(String s, float v) {

        return this;
    }

    @Override
    public Preferences putStringSet(String s, Set<String> set) {

        return this;
    }

    @Override
    public Preferences delete(String s) {
        return mEditor.delete(s);
    }

    @Override
    public Preferences clear() {
        return mEditor.clear();
    }

    @Override
    public void flush() {
        mEditor.flush();
    }

    @Override
    public boolean flushSync() {
        return mEditor.flushSync();
    }

    @Override
    public boolean hasKey(String s) {
        return mEditor.hasKey(s);
    }


    @Override
    public String getString(String key, String defValue) {
        String v = (String) getValue(PATH_GET_STRING, key, defValue);
        return v != null ? v : defValue;
    }

    // @Override //
    public Set<String> getStringSet(String key, Set<String> defValues) {
        synchronized (this) {
            @SuppressWarnings("unchecked")
            Set<String> v = (Set<String>) getValue(PATH_GET_STRING, key, defValues);
            return v != null ? v : defValues;
        }
    }

    @Override
    public int getInt(String key, int defValue) {
//        Util.log("getint  ---key=%s,defValue=%s", key, defValue);
        Integer v = (Integer) getValue(PATH_GET_INT, key, defValue);
        return v != null ? v : defValue;
    }

    @Override
    public long getLong(String key, long defValue) {
        Long v = (Long) getValue(PATH_GET_LONG, key, defValue);
        return v != null ? v : defValue;
    }

    @Override
    public float getFloat(String key, float defValue) {
        Float v = (Float) getValue(PATH_GET_FLOAT, key, defValue);
        return v != null ? v : defValue;
    }

    @Override
    public boolean getBoolean(String key, boolean defValue) {
        Boolean v = (Boolean) getValue(PATH_GET_BOOLEAN, key, defValue);
        return v != null ? v : defValue;
    }


    public final class EditorImpl implements Preferences {
        private final Map<String, Object> mModified = new HashMap<String, Object>();
        private boolean mClear = false;

        @Override
        public Preferences putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

        // @Override //
        public Preferences putStringSet(String key, Set<String> values) {
            synchronized (this) {
                mModified.put(key, (values == null) ? null : new HashSet<String>(values));
                return this;
            }
        }

        @Override
        public Preferences delete(String s) {
            synchronized (this) {
                mModified.put(s, null);
                return this;
            }
        }

        @Override
        public int getInt(String s, int i) {
            return 0;
        }

        @Override
        public String getString(String s, String s1) {
            return null;
        }

        @Override
        public boolean getBoolean(String s, boolean b) {
            return false;
        }

        @Override
        public float getFloat(String s, float v) {
            return 0;
        }

        @Override
        public long getLong(String s, long l) {
            return 0;
        }

        @Override
        public Set<String> getStringSet(String s, Set<String> set) {
            return null;
        }

        @Override
        public Map<String, ?> getAll() {
            return null;
        }

        @Override
        public Preferences putInt(String key, int value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

        @Override
        public Preferences putLong(String key, long value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

        @Override
        public Preferences putFloat(String key, float value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

        @Override
        public Preferences putBoolean(String key, boolean value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }


        @Override
        public Preferences clear() {
            synchronized (this) {
                mClear = true;
                return this;
            }
        }

        @Override
        public void flush() {
            setValue(PATH_APPLY);
        }

        @Override
        public boolean flushSync() {
            return setValue(PATH_COMMIT);
        }

        @Override
        public boolean hasKey(String s) {
            return false;
        }

        @Override
        public void registerObserver(PreferencesObserver preferencesObserver) {

        }

        @Override
        public void unregisterObserver(PreferencesObserver preferencesObserver) {

        }

        private boolean setValue(String pathSegment) {
            if (mIsSafeMode) { // 如果设备处在“安全模式”，返回false；
                return false;
            } else {
                synchronized (MultiProcessSharedPreferences.this) {
                    checkInitAuthority(mContext);
                    String[] selectionArgs = new String[]{String.valueOf(mMode), String.valueOf(mClear)};
                    synchronized (this) {
                        Uri uri = Uri.appendEncodedPathToUri(Uri.appendEncodedPathToUri(AUTHORITY_URI, mName), pathSegment);
                        ValuesBucket values =
                                ReflectionUtil.contentValuesNewInstance((HashMap<String, Object>) mModified);
                        try {
                            return DataAbilityHelper.creator(mContext).update(uri, values, null) > 0;
                        } catch (DataAbilityRemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return false;
        }
    }


    private Object getValue(String pathSegment, String key, Object defValue) {
        if (mIsSafeMode) { // 如果设备处在“安全模式”，返回null；
            return null;
        } else {
            checkInitAuthority(mContext);
            Object v = null;
            Uri uri = Uri.appendEncodedPathToUri(Uri.appendEncodedPathToUri(AUTHORITY_URI, mName), pathSegment);
            String[] selectionArgs =
                    new String[]{String.valueOf(mMode), key, defValue == null ? null : String.valueOf(defValue)};
            ResultSet cursor = null;
            try {
                cursor = DataAbilityHelper.creator(mContext).query(uri, selectionArgs, null);
            } catch (DataAbilityRemoteException e) {
                e.printStackTrace();
            }
            if (cursor != null) {
                try {
                    PacMap bundle = cursor.getExtensions();
                    if (bundle != null) {
                        switch (pathSegment) {
                            case PATH_GET_FLOAT:
                                v = bundle.getFloatValue(KEY);
                                break;
                            case PATH_GET_LONG:
                                v = bundle.getLongValue(KEY);
                                break;
                            case PATH_GET_BOOLEAN:
                                v = bundle.getBooleanValue(KEY);
                                break;
                            case PATH_GET_INT:
                                v = bundle.getIntValue(KEY);
                                break;
                            case PATH_GET_STRING:
                                v = bundle.getString(KEY);
                                break;
                        }
                        bundle.clear();
                    }
                } catch (Exception e) {
                }
                cursor.close();
            }
            return v != null ? v : defValue;
        }
    }

    private String makeAction(String name) {
        return String.format("%1$s_%2$s", MultiProcessSharedPreferences.class.getName(), name);
    }


    @Override
    public ResultSet query(Uri uri, String[] projection, DataAbilityPredicates sortOrder) {

        String name = uri.getDecodedPathList().get(0);
        String key = projection[1];
        String defValue = projection[2];
//        Util.log("query uri:" + uri + ", name:" + name + "key:" + key + "defValue" + defValue);
        PacMap bundle = new PacMap();
        int match = mUriMatcher.getPathId(uri.getLastPath());

        switch (match) {
            case GET_ALL:
                bundle.putSerializableObject(KEY,
                        (HashMap<String, ?>) databaseHelper.getPreferences(name).getAll());
                break;
            case GET_STRING:
                bundle.putString(KEY, String.valueOf(databaseHelper.getPreferences(name).getString(key, String.valueOf(defValue))));
                break;
            case GET_INT:


                bundle.putIntValue(KEY,
                        databaseHelper.getPreferences(name).getInt(key, Integer.parseInt(defValue)));
                break;
            case GET_LONG:
                bundle.putLongValue(KEY,
                        databaseHelper.getPreferences(name).getLong(key, Long.parseLong(defValue)));
                break;
            case GET_FLOAT:
                bundle.putFloatValue(
                        KEY, databaseHelper.getPreferences(name).getFloat(key, Float.parseFloat(defValue)));
                break;
            case GET_BOOLEAN:
                bundle.putBooleanValue(
                        KEY, databaseHelper.getPreferences(name).getBoolean(key, Boolean.parseBoolean(defValue)));
                break;
            case CONTAINS:
                bundle.putBooleanValue(KEY, databaseHelper.getPreferences(name).hasKey(key));
                break;
            case REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
                checkInitListenersCount();
                Integer countInteger = mListenersCount.get(name);
                int count = (countInteger == null ? 0 : countInteger) + 1;
                mListenersCount.put(name, count);
                countInteger = mListenersCount.get(name);
                bundle.putBooleanValue(KEY, count == (countInteger == null ? 0 : countInteger));
            }
            break;
            case UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
                checkInitListenersCount();
                Integer countInteger = mListenersCount.get(name);
                int count = (countInteger == null ? 0 : countInteger) - 1;
                if (count <= 0) {
                    mListenersCount.remove(name);
                    bundle.putBooleanValue(KEY, !mListenersCount.containsKey(name));
                } else {
                    mListenersCount.put(name, count);
                    countInteger = mListenersCount.get(name);
                    bundle.putBooleanValue(KEY, count == (countInteger == null ? 0 : countInteger));
                }
            }
            break;
            default:
                throw new IllegalArgumentException("This is Unknown Uri：" + uri.toString());
        }
        return new BundleCursor(bundle);
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("No external call");
    }

    @Override
    public int insert(Uri uri, ValuesBucket values) {
        throw new UnsupportedOperationException("No external insert");
    }


    @SuppressWarnings("unchecked")
    @Override
    public int update(Uri uri, ValuesBucket values, DataAbilityPredicates dataAbilityPredicates) {
//        Util.log("update ### uri:" + uri + ", values:" + values);
        int result = 0;
        String name = uri.getDecodedPathList().get(0);
//        int mode = Integer.parseInt(values.get[0]);
        Preferences preferences = databaseHelper.getPreferences(name);
        int match = mUriMatcher.getPathId(uri.getLastPath());
        switch (match) {
            case APPLY:
            case COMMIT:
                boolean hasListeners =
                        mListenersCount != null && mListenersCount.get(name) != null && mListenersCount.get(name) > 0;
                ArrayList<String> keysModified = null;
                Map<String, Object> map = new HashMap();
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    map = (Map<String, Object>) preferences.getAll();
                }

//                boolean clear = Boolean.parseBoolean(selectionArgs[1]);
//                if (clear) {
//                    if (hasListeners && map != null && !map.isEmpty()) {
//                        for (Map.Entry<String, Object> entry : map.entrySet()) {
//                            keysModified.add(entry.getKey());
//                        }
//                    }
//                    preferences.clear();
//                }
                for (Map.Entry<String, Object> entry : values.getAll()) {
                    String k = entry.getKey();
                    Object v = entry.getValue();
                    //  5.L_preview : "this" is the magic value for a removal mutation. In addition,
                    // setting a value to "null" for a given key is specified to be
                    // equivalent to calling remove on that key.
                    if (v instanceof EditorImpl || v == null) {
                        preferences.delete(k);
                        if (hasListeners && map != null && map.containsKey(k)) {
                            keysModified.add(k);
                        }
                    } else {
                        if (hasListeners && map != null
                                && (!map.containsKey(k) || (map.containsKey(k) && !v.equals(map.get(k))))) {
                            keysModified.add(k);
                        }
                    }

                    if (v instanceof String) {
                        preferences.putString(k, (String) v);
                    } else if (v instanceof Set) {
                        ReflectionUtil.editorPutStringSet(preferences, k,
                                (Set<String>) v);
                    } else if (v instanceof Integer) {
                        preferences.putInt(k, (Integer) v);
                    } else if (v instanceof Long) {
                        preferences.putLong(k, (Long) v);
                    } else if (v instanceof Float) {
                        preferences.putFloat(k, (Float) v);
                    } else if (v instanceof Boolean) {
                        preferences.putBoolean(k, (Boolean) v);
                    }
                }
                if (hasListeners && keysModified.isEmpty()) {
                    result = 1;
                } else {
                    switch (match) {
                        case APPLY:
//                            ReflectionUtil.editorApply(preferences); //
                            preferences.flush();
                            result = 1;
                            // Okay to notify the listeners before it's hit disk
                            // because the listeners should always get the same
                            // SharedPreferences instance back, which has the
                            // changes reflected in memory.
                            notifyListeners(name, keysModified);
                            break;
                        case COMMIT:
                            if (preferences.flushSync()) {
                                result = 1;
                                notifyListeners(name, keysModified);
                            }
                            break;
                    }
                }
                values.clear();
                break;
            default:
                throw new IllegalArgumentException("This is Unknown Uri：" + uri);
        }
        return result;
    }


    private void checkInitListenersCount() {
        if (mListenersCount == null) {
            mListenersCount = new HashMap<String, Integer>();
        }
    }

    private void notifyListeners(String name, ArrayList<String> keysModified) {
        if (keysModified != null && !keysModified.isEmpty()) {

            CommonEventData commonEventData = new CommonEventData();
            commonEventData.getIntent().setBundle(getContext().getBundleName());
            commonEventData.getIntent().setAction(makeAction(name));
            commonEventData.getIntent().setParam(KEY_NAME, name);
            commonEventData.getIntent().setParam(KEY, keysModified);

            try {
                publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    private final class BundleCursor extends TableResultSet {
        private PacMap mBundle;

        public BundleCursor(PacMap extras) {
            super(new String[]{}, 0);
            mBundle = extras;
        }

        @Override
        public PacMap getExtensions() {
            return mBundle;
        }


    }
}

